package com.logicbus.backend.websocket;

import com.alogic.xscript.AbstractLogiclet;
import com.alogic.xscript.ExecuteWatcher;
import com.alogic.xscript.Logiclet;
import com.alogic.xscript.LogicletContext;
import com.alogic.xscript.doc.XsObject;
import com.alogic.xscript.plugins.Segment;
import com.anysoft.util.Properties;
import com.anysoft.util.PropertiesConstants;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.util.encoders.Base64;

import javax.websocket.CloseReason;
import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import java.nio.ByteBuffer;

/**
 * WebSocket的远程操作
 *
 * @since 1.6.14.1 [20210310 duanyy] <br>
 */
public class Remote extends Segment {
    public Remote(String tag, Logiclet p) {
        super(tag, p);

        registerModule("ws-text",SendText.class);
        registerModule("ws-data",SendData.class);
        registerModule("ws-ping",SendPing.class);
        registerModule("ws-pong",SendPong.class);
        registerModule("ws-close",SendClose.class);
    }

    /**
     * 操作虚基类
     */
    public abstract static class Operation extends AbstractLogiclet{

        protected String pid = WSEndpointBase.SESSIONID;

        public Operation(String tag, Logiclet p) {
            super(tag, p);
        }

        @Override
        public void configure(Properties p){
            super.configure(p);
            pid = PropertiesConstants.getString(p,"pid",pid,true);
        }

        @Override
        protected void onExecute(XsObject root, XsObject current, final LogicletContext ctx, final ExecuteWatcher watcher){
            Session session = ctx.getObject(pid);
            if (session != null){
                onExecute(session,root,current,ctx,watcher);
            }
        }

        protected abstract void onExecute(Session session, XsObject root,
                                          XsObject current, LogicletContext ctx, ExecuteWatcher watcher);
    }

    /**
     * 发送文本
     */
    public static class SendText extends Operation{
        protected String $text;
        protected String $finish = "true";

        public SendText(String tag, Logiclet p) {
            super(tag, p);
        }

        @Override
        public void configure(Properties props){
            super.configure(props);
            $text = PropertiesConstants.getRaw(props,"text","");
            $finish = PropertiesConstants.getRaw(props,"finish",$finish);
        }

        @Override
        protected void onExecute(Session session, XsObject root, XsObject current, LogicletContext ctx, ExecuteWatcher watcher) {
            String text = PropertiesConstants.transform(ctx,$text,"");
            if (StringUtils.isNotEmpty(text)){
                RemoteEndpoint.Basic basic = session.getBasicRemote();
                try {
                    basic.sendText(text,PropertiesConstants.transform(ctx,$finish,true));
                }catch (Exception ex){
                    log("Failed to send message to remote endpoint:" + ex.getMessage());
                }
            }
        }
    }

    /**
     * 发送数据
     */
    public static class SendData extends Operation{
        protected String $id;
        protected String $finish = "true";

        public SendData(String tag, Logiclet p) {
            super(tag, p);
        }

        @Override
        public void configure(Properties props){
            super.configure(props);
            $id = PropertiesConstants.getRaw(props,"id","");
            $finish = PropertiesConstants.getRaw(props,"finish",$finish);
        }

        @Override
        protected void onExecute(Session session, XsObject root, XsObject current, LogicletContext ctx, ExecuteWatcher watcher) {
            String id = PropertiesConstants.transform(ctx,$id,"");
            if (StringUtils.isNotEmpty(id)){
                String data = PropertiesConstants.getString(ctx,id,"");
                if (StringUtils.isNotEmpty(data)) {
                    RemoteEndpoint.Basic basic = session.getBasicRemote();
                    try {
                        byte[] bytes = Base64.decode(data);
                        basic.sendBinary(ByteBuffer.wrap(bytes),PropertiesConstants.transform(ctx, $finish, true));
                    } catch (Exception ex) {
                        log("Failed to send message to remote endpoint:" + ex.getMessage());
                    }
                }
            }
        }
    }

    /**
     * 发送Ping数据
     */
    public static class SendPing extends Operation{
        protected String $id;

        public SendPing(String tag, Logiclet p) {
            super(tag, p);
        }

        @Override
        public void configure(Properties props){
            super.configure(props);
            $id = PropertiesConstants.getRaw(props,"id","");
        }

        @Override
        protected void onExecute(Session session, XsObject root, XsObject current, LogicletContext ctx, ExecuteWatcher watcher) {
            String id = PropertiesConstants.transform(ctx,$id,"");
            if (StringUtils.isNotEmpty(id)){
                String data = PropertiesConstants.getString(ctx,id,"");
                if (StringUtils.isNotEmpty(data)) {
                    RemoteEndpoint.Basic basic = session.getBasicRemote();
                    try {
                        byte[] bytes = Base64.decode(data);
                        basic.sendPing(ByteBuffer.wrap(bytes));
                    } catch (Exception ex) {
                        log("Failed to send ping to remote endpoint:" + ex.getMessage());
                    }
                }
            }
        }
    }

    /**
     * 发送Pong数据
     */
    public static class SendPong extends Operation{
        protected String $id;

        public SendPong(String tag, Logiclet p) {
            super(tag, p);
        }

        @Override
        public void configure(Properties props){
            super.configure(props);
            $id = PropertiesConstants.getRaw(props,"id","");
        }

        @Override
        protected void onExecute(Session session, XsObject root, XsObject current, LogicletContext ctx, ExecuteWatcher watcher) {
            String id = PropertiesConstants.transform(ctx,$id,"");
            if (StringUtils.isNotEmpty(id)){
                String data = PropertiesConstants.getString(ctx,id,"");
                if (StringUtils.isNotEmpty(data)) {
                    RemoteEndpoint.Basic basic = session.getBasicRemote();
                    try {
                        byte[] bytes = Base64.decode(data);
                        basic.sendPong(ByteBuffer.wrap(bytes));
                    } catch (Exception ex) {
                        log("Failed to send ping to remote endpoint:" + ex.getMessage());
                    }
                }
            }
        }
    }

    /**
     * 发送Pong数据
     */
    public static class SendClose extends Operation{
        protected String $status;
        protected String $reason;

        public SendClose(String tag, Logiclet p) {
            super(tag, p);
        }

        @Override
        public void configure(Properties props){
            super.configure(props);
            $status = PropertiesConstants.getRaw(props,"status","");
            $reason = PropertiesConstants.getRaw(props,"reason","");
        }

        @Override
        protected void onExecute(Session session, XsObject root, XsObject current, LogicletContext ctx, ExecuteWatcher watcher) {
            int status = PropertiesConstants.getInt(ctx,$status,0);
            try {
                if (status >= 1000 && status <= 4999) {
                    CloseReason.CloseCode closeCode = CloseReason.CloseCodes.getCloseCode(status);
                    session.close(new CloseReason(closeCode,PropertiesConstants.transform(ctx,$reason,"")));
                }else{
                    session.close();
                }
            } catch (Exception ex) {
                log("Failed to close remote endpoint:" + ex.getMessage());
            }
        }
    }
}
